---
title: Adding Keystatic to a Next.js project
summary: Integrating Keystatic with an existing Next.js 13 project.
---
{% aside icon="☝️" %}
This guide assumes you have an existing Next.js 14 project, and are using the `app` directory.
{% /aside %}

If you don't have an existing Next.js project, you can create a new one with the following command:

```bash
npx create-next-app@latest
```

## Installing dependencies

Install two Keystatic packages and `@markdoc/markdoc`:

```bash
npm install @keystatic/core @keystatic/next @markdoc/markdoc
```

## Creating a Keystatic config file

A Keystatic config file is required to define your content schema. This file will also allow you to connect a project to a specific GitHub repository (if you decide to do so).

Create a file called `keystatic.config.ts` in the root of the project and add the following code to define both your storage type (`local`) and a single content collection (`posts`):

```typescript
// keystatic.config.ts
import { config, fields, collection } from '@keystatic/core';

export default config({
  storage: {
    kind: 'local',
  },
  collections: {
    posts: collection({
      label: 'Posts',
      slugField: 'title',
      path: 'src/content/posts/*',
      format: { contentField: 'content' },
      schema: {
        title: fields.slug({ name: { label: 'Title' } }),
        content: fields.markdoc({ label: 'Content' }),
      },
    }),
  },
});
```

Keystatic is now configured to manage your content based on your schema.

---

## Setting up the Keystatic Admin UI

First, create a `src/app/keystatic/keystatic.ts` file:

```ts
// src/app/keystatic/keystatic.ts
"use client";

import { makePage } from "@keystatic/next/ui/app";
import config from "../../../keystatic.config";

export default makePage(config);
```

Next, create a layout file called `src/app/keystatic/layout.tsx`:

```tsx
// src/app/keystatic/layout.tsx
import KeystaticApp from "./keystatic";

export default function Layout() {
  return (
    <KeystaticApp />
  );
}

```

Next, create a page called `src/app/keystatic/[[...params]]/page.tsx`:

```jsx
// src/app/keystatic/[[...params]]/page.tsx

export default function Page() {
  return null;
}

```

Finally, create an API route called `src/app/api/keystatic/[...params]/route.ts`

```tsx
// src/app/api/keystatic/[...params]/route.ts
import { makeRouteHandler } from '@keystatic/next/route-handler';
import config from '../../../../../keystatic.config';

export const { POST, GET } = makeRouteHandler({
  config,
});

```

You can now launch the Keystatic Admin UI. Start the Next dev server:

```bash
npm run dev
```

Visit `http://127.0.0.1:3000/keystatic` to see the Keystatic Admin UI running.

---

## Creating a new post

{% aside icon="☝️" %}
In our Keystatic config file, we've set the `path` property for our `posts` collection to `src/content/posts/*`.

As a result, creating a new post from the Keystatic Admin UI should create a new `content` directory in the `src` directory, with the new post `.mdoc` file inside!
{% /aside %}

Go ahead — create a new post from the Admin UI, and hit save.

You will find your new post inside the `src/content/posts` directory:

```bash
src
└── content
    └── posts
        └── my-first-post.mdoc
```

Navigate to that file in your code editor and verify that you can see the Markdown content you entered. For example:

```markdown
---
title: My First Post
---

This is my very first post. I am **super** excited.
```

---

## Rendering Keystatic content

{% aside icon="💡" %}
Keystatic provides a [Reader API](/docs/reader-api) to bring content to the front end. As it is a Node API it must be run server-side.
{% /aside %}

### Displaying a collection list

The following example displays a list of each post title, with a link to an individual post page:

```tsx
// src/app/posts/page.tsx
import { createReader } from '@keystatic/core/reader';
import keystaticConfig from '../../../keystatic.config';

import Link from 'next/link';

// 1. Create a reader
const reader = createReader(process.cwd(), keystaticConfig);

export default async function Page() {
  
  // 2. Read the "Posts" collection
  const posts = await reader.collections.posts.all();
  return (
    <ul>
      {posts.map(post => (
        <li key={post.slug}>
          <Link href={`/posts/${post.slug}`}>{post.entry.title}</Link>
        </li>
      ))}
    </ul>
  );
}
```

### Displaying a single collection entry

To display content from an individual post, you can use Markdoc's `transform` and `renderers.react` functions:

```tsx
// src/app/posts/[slug]/page.tsx
import { createReader } from "@keystatic/core/reader";
import React from "react";
import Markdoc from "@markdoc/markdoc";

import keystaticConfig from "../../../../keystatic.config";

const reader = createReader(process.cwd(), keystaticConfig);

export default async function Post({ params }: { params: { slug: string } }) {
  const post = await reader.collections.posts.read(params.slug);
  if (!post) {
    return <div>No Post Found</div>;
  }
  const { node } = await post.content();
  const errors = Markdoc.validate(node);
  if (errors.length) {
    console.error(errors);
    throw new Error('Invalid content');
  }
  const renderable = Markdoc.transform(node);
  return (
    <>
      <h1>{post.title}</h1>
      {Markdoc.renderers.react(renderable, React)}
      <hr />
      <a href={`/posts`}>Back to Posts</a>
    </>
  );
}
```

---

## Deploying Keystatic + Next.js

Because Keystatic needs to run serverside code and [Next.js API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes), you will need to ensure that your hosting provider supports Node.js.

You will also probably want to [connect Keystatic to GitHub](/docs/github-mode) so you can manage content on the deployed instance of the project.
